#pragma once 

#include <vector>
#include <queue>
#include <mutex>
#include <functional>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t num_threads) 
        : m_numThreads(num_threads) {
        for (size_t i = 0; i < num_threads; ++i) {
            m_threads.emplace_back([this]() {
                while (true) { 
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(m_mutex);
                        m_cv.wait(lock, [this]() { return m_stop || !m_tasks.empty(); });
                        if (m_stop && m_tasks.empty()) return ;
                        task = std::move(m_tasks.front());
                        m_tasks.pop();
                    }
                    task();
                }
            });
        }
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            m_stop = true;
        }
        m_cv.notify_all();
        for (std::thread& t : m_threads) {
            if (t.joinable()) t.join();
        }
    }


    template <typename Func>
    void enqueue(Func&& func) {
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            if (m_stop) throw std::runtime_error("ThreadPool is stopped.");
            m_tasks.emplace(std::forward<Func>(func));
        }
        m_cv.notify_one();
    }


private:
    size_t m_numThreads;
    std::vector<std::thread> m_threads;
    std::queue<std::function<void()>> m_tasks;
    std::mutex m_mutex;
    std::condition_variable m_cv;
    bool m_stop = false;
};
